import numpy as np

from fund.get_history_data import read_funds_from_yml_list
from my_const import MY_PREFER_FUND_YML_LIST, ENDPOINT, DEFAULT_PARAMS, DESC, DYNAMIC_PARAMS
from util.csv_util import csv_2_df
from util.date_util import *
from fund.strategy.strategy_util import *
from util.log_util import get_the_logger

logger = get_the_logger(__name__)


# 均值策略变体：
# 以30天均线为例子
# 每天都需要计算 r = 当天价格 / 30天均线
# 如果 r <= 1, 则说明低于均线了，可以买入，此时还要确定买入多少（最低买y元，最高买y2元）
# 该策略的想法是，当r越小时，就买入更多
# 所以要回顾最近一段recent时间内（比如一年内）的r值的最小值r_min
# 如果发现r_min大于等于1，就只买入基础的y元
# 如果发现r_min小于1，则要判断当天的r值是否足够接近r_min
# 当天的r值越接近r_min，则买入金额越多，但是最大不能超过y2
# 调整买入金额的具体增长曲线请你自己想一个
def strategy_based_on_mean_pro(df, x=0, y=100, y2=1000, days=30, recent=250, start_date=None, end_date=None):
    # --- 初始化 ---
    start_date, end_date = process_dates(df, start_date, end_date)
    total_cost = 0.0
    total_units = 0.0
    attempts_times = 0

    # 获取日期索引位置
    start_idx = df.index.get_loc(start_date)
    end_idx = df.index.get_loc(end_date)

    start_idx = max(start_idx, days + recent)  # 确保足够历史数据

    if start_idx >= end_idx:
        return empty_result(start_date, end_date)

    # --- 预计算关键数据 ---
    # 转换为numpy数组提升性能
    net_values = df['单位净值'].values.astype(np.float64)

    # 预计算均线（避免重复切片）
    ma = pd.Series(net_values).rolling(days).mean().values  # 使用pandas内置滚动均线

    # 预计算r值（避免循环内重复计算）
    r = net_values / ma  # 全量r值

    # 预计算滚动r_min（使用pandas向量化操作）
    r_min_series = pd.Series(r).rolling(recent, min_periods=1).min().values

    # --- 核心逻辑优化 ---
    # 筛选有效区间（start_idx到end_idx）
    for current_pos in range(start_idx, end_idx):
        # 跳过无效数据（均线无法计算）
        if np.isnan(ma[current_pos]):
            raise Exception("均线无法计算")
        if r[current_pos] >= 1.0:
            continue

        # 获取历史r_min（直接查预计算结果）
        window_start = max(current_pos - recent, 0)
        r_min = r_min_series[window_start:current_pos].min()
        r_min = r_min if not np.isnan(r_min) else 1.0  # 处理NaN

        # 动态调整金额
        if r_min >= 1.0:
            investment = y
        else:
            # 计算偏离度（添加极小值保护）
            denominator = max(1.0 - r_min, 1e-6)
            deviation = (r[current_pos] - r_min) / denominator
            # 指数衰减曲线
            weight = np.exp(-3 * deviation)
            investment = y + (y2 - y) * weight
            investment = np.clip(investment, y, y2)

        # 买入操作
        current_net_value = net_values[current_pos]
        total_units += investment / current_net_value
        total_cost += investment
        attempts_times += 1

    # --- 收益计算 ---
    last_net_value = net_values[end_idx - 1]
    final_asset = total_units * last_net_value
    total_profit = final_asset - total_cost
    total_return_rate = total_profit / total_cost if total_cost != 0 else 0
    investment_days = (end_date - start_date).days
    annual_return_rate = (final_asset / total_cost) ** (365 / investment_days) - 1 if total_cost > 0 else 0

    return {
        "开始时间": start_date.strftime('%Y-%m-%d'),
        "结束时间": end_date.strftime('%Y-%m-%d'),
        "总投入（买基金花费）": round(total_cost, 2),
        "总收益（卖基金所得）": round(final_asset, 2),
        "最终持有基金份额": round(total_units, 4),
        "最终资产": round(final_asset, 2),
        "总利润": round(total_profit, 2),
        "总收益率": round(total_return_rate, 4),
        "年化收益率": round(annual_return_rate, 4),
        "买入次数": attempts_times
    }


def backtest():
    from fund.strategy.strategy_factory import STRATEGY_MAP, DCA

    # ---------------------- 1. 基础配置（仅需调整这里） ----------------------
    PERIOD = "daily"
    START_DATE = before_1y_yyyymmdd()  # 1年前
    END_DATE = yesterday_yyyymmdd()  # 昨天
    FUND_LIST = MY_PREFER_FUND_YML_LIST  # 你的基金列表

    # ---------------------- 2. 动态生成策略列表（适配新STRATEGY_MAP） ----------------------
    strategy_list = []  # 格式：[(策略标识, 显示名称, 策略函数, 合并后参数), ...]
    for strategy_type, config in STRATEGY_MAP.items():
        func = config[ENDPOINT]  # 策略函数
        strategy_desc = config[DESC]  # 基础描述（如“基于均值”）
        fixed_params = config[DEFAULT_PARAMS]  # 固定参数（如higher_money=0）
        dynamic_config = config[DYNAMIC_PARAMS]  # 动态参数（MA是字典，其他是None）

        # 情况1：无动态参数（DCA、DROP）→ 仅生成1组策略
        if not dynamic_config:
            strategy_id = f"{strategy_type}_BASE"
            display_name = strategy_desc
            # 合并固定参数（无需加动态参数）
            final_params = fixed_params.copy()
            strategy_list.append((strategy_id, display_name, func, final_params))

        # 情况2：有动态参数（MA策略，DYNAMIC_PARAMS是字典）→ 生成多组策略
        else:
            # 提取动态参数的“key”和“值列表”（MA的key是ma_range，值是[30,60,90]）
            # 假设动态参数字典仅1个key（如ma_range），如需支持多key可扩展
            dynamic_key = next(iter(dynamic_config.keys()))  # 获取第一个key（ma_range）
            dynamic_values = dynamic_config[dynamic_key]  # 获取值列表（[30,60,90]）

            # 遍历动态值，生成多组策略
            for dyn_val in dynamic_values:
                strategy_id = f"{strategy_type}_{dynamic_key}_{dyn_val}"  # 标识：MA_ma_range_30
                display_name = f"{strategy_desc}-{dyn_val}天"
                # 合并“固定参数+动态参数”（如{higher_money:0, lower_money:200, ma_range:30}）
                final_params = fixed_params.copy()
                final_params[dynamic_key] = dyn_val  # 加入动态参数（ma_range=30）
                strategy_list.append((strategy_id, display_name, func, final_params))

    # ---------------------- 3. 加载基金信息（获取基金代码、名称、起止时间） ----------------------
    fund_codes, fund_names = read_funds_from_yml_list(FUND_LIST)  # 基金代码列表、名称映射
    fund_date_map = {}  # 基金代码 → 实际起止时间（从定投策略获取，确保日期有效）

    # 提前获取每个基金的实际起止时间（复用DCA策略的日期处理逻辑）
    dca_func = STRATEGY_MAP[DCA][ENDPOINT]  # 获取定投函数
    dca_fixed_params = STRATEGY_MAP[DCA][DEFAULT_PARAMS]  # 定投固定参数（money=100）
    for code in fund_codes:
        # 读取基金历史数据
        csv_path = f"fund/history_data/{code}-{PERIOD}.csv"
        df = csv_2_df(csv_path)

        # 调用定投策略仅获取日期（不关心收益）
        date_result = dca_func(df, start_date=START_DATE, end_date=END_DATE, **dca_fixed_params)

        # 存储“基金代码→起止时间”
        fund_date_map[code] = f"{date_result['开始时间']}~{date_result['结束时间']}"

    # ---------------------- 4. 统一回测所有策略（无if-elif，全靠映射表） ----------------------
    result_store = {}  # 存储结果：{策略显示名: [基金1结果, 基金2结果, ...]}
    for _, display_name, func, params in strategy_list:
        fund_results = []  # 该策略下所有基金的结果
        for code in fund_codes:
            # 读取基金历史数据
            csv_path = f"fund/history_data/{code}-{PERIOD}.csv"
            df = csv_2_df(csv_path)

            # 调用策略函数（统一传递：df + 策略参数 + 日期参数）
            # 关键：**params 会自动解包参数（如higher_money=0, ma_range=30等）
            strategy_res = func(
                df=df,
                start_date=START_DATE,
                end_date=END_DATE,
                **params
            )

            # 格式化结果（年化收益率+买入次数/工作日数）
            start_ymd = y_m_d_to_ymd(strategy_res["开始时间"])
            end_ymd = y_m_d_to_ymd(strategy_res["结束时间"])
            workday_total = workday_of_range(start_ymd, end_ymd)
            result_str = f"{strategy_res['年化收益率'] * 100:.2f}% {strategy_res['买入次数']}/{workday_total}"
            fund_results.append(result_str)

        # 存储该策略的所有基金结果
        result_store[display_name] = fund_results

    # ---------------------- 5. 生成CSV并保存 ----------------------
    # 构建表头：基金信息 + 起止时间 + 所有策略显示名
    table_headers = ["基金信息", "起止时间"] + list(result_store.keys())
    final_table = [table_headers]

    # 构建内容行：每个基金1行
    for idx, code in enumerate(fund_codes):
        fund_info = f"{fund_names[code]}{code}"  # 基金名+代码（如“富国新兴产业001040”）
        fund_time = fund_date_map[code]  # 基金起止时间
        # 提取该基金在所有策略下的结果
        fund_row = [result_store[disp_name][idx] for disp_name in result_store.keys()]
        # 拼接该行数据
        final_table.append([fund_info, fund_time] + fund_row)

    # 保存为CSV（避免中文乱码用utf-8-sig）
    output_path = f"fund/output/strategy_backtest-{START_DATE}-{END_DATE}.csv"

    pd.DataFrame(final_table).to_csv(
        output_path,
        index=False,  # 不保存行索引
        header=False,  # 不重复保存表头（已手动加在final_table里）
        encoding="utf-8-sig"
    )

    logger.info(f"All backtesting are completed, results are saved in：{output_path}")


if __name__ == "__main__":
    backtest()
